package citic;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;

public class SFTPFileTransfer extends FileTransferAdapter {
	private static final Log logger = LogFactory.getLog(SFTPFileTransfer.class);
	private static final int timeout = 60000;
	private ChannelSftp sftp;
	
	private String _root = "/";
	private Session _sshSession;
	
	public ChannelSftp getSftp() {
		return sftp;
	}
	public void setSftp(ChannelSftp sftp) {
		this.sftp = sftp;
	}
	public Session get_sshSession() {
		return _sshSession;
	}
	public void set_sshSession(Session _sshSession) {
		this._sshSession = _sshSession;
	}
	
	/**
	 * 连接sftp,并进入根目录
	 * @param host 主机号
	 * @param userName 用户名
	 * @param password 密码
	 * @param port 端口
	 * @return
	 */
	protected boolean connect(String host, int port, String userName, String password)
			{
		try {
			try {
				sessionConntect(host,userName,password, port);
			} catch (JSchException e) {
				logger.info("创建session失败，进行重连:"+e);
				sessionConntect(host,userName,password, port);
			}
			try{
				channelConntect();
			}catch(JSchException e){
				logger.info("创建channel失败，进行重连:"+e);
				sessionConntect(host,userName,password, port);
				channelConntect();
			}
			_root = FilenameUtils.separatorsToUnix(sftp.pwd());
			sftp.cd(_root);
			logger.info("建立SFTP连接成功");
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("建立SFTP连接错误" + e);
			return false;
		}
	}
	
	private void channelConntect() throws JSchException{
		Channel channel = _sshSession.openChannel("sftp");
		if(!channel.isConnected()){
		    channel.connect();
		}
		sftp = (ChannelSftp) channel;
	}
	
	private void sessionConntect(String host,
			String userName, String password, int port) throws JSchException{
		JSch jsch = new JSch();
		_sshSession = jsch.getSession(userName, host, port);
		_sshSession.setPassword(password);
		Properties sshConfig = new Properties();
		sshConfig.put("StrictHostKeyChecking", "no");
		_sshSession.setConfig(sshConfig);
		_sshSession.setServerAliveInterval(1000);
		if(!_sshSession.isConnected())
		{
		_sshSession.connect(timeout);
	    }
	}
	
	/**
	 * 上传文件
	 * @param remote 远程文件路径
	 * @param localFile 本地文件路径
	 */
	public boolean upload(String  localFile,String remoteFile) {
		boolean result = false;
		String ftpDir = getUnixPath(remoteFile);
		String fileName = FilenameUtils.getName(remoteFile);
		try {
			
			// 定位到SFTP目录
			boolean flag = mkDir(ftpDir);
			if (flag) {// 本地文件全名
				File uploadFile = new File(localFile);
				result = this.upload(uploadFile,fileName);// 上传
				if (result) {
					logger.info("上传文件成功!上传文件" + localFile + "到目录" + ftpDir);
				}
				sftp.cd(_root);
			}

		} catch (Exception e) {
			logger.error("上传文件失败!上传文件" + localFile + "到目录" + ftpDir
					+ "错误详情：" + e.getMessage());
		}
		return result;
	}

	/**
	 * 删除文件
	 * @param remoteDir 远程目录
	 * @param deleteFile 文件名
	 */
	void removeFile(String remoteDir,String deleteFile) {
		try {
			boolean flg = this.cd(remoteDir);//进入目录
			if (flg) {
				rm(deleteFile);//删除文件
				logger.debug("delete "+deleteFile+" success!");
			}
		} catch (Exception e) {
			logger.error("delete "+deleteFile+" fail!"+e.getMessage());
		}
	}


	/**
	 * 断开连接
	 */
	public void disconnect() {
		if (sftp != null) {
			if (sftp.isConnected()) {
				sftp.disconnect();

			} 
		}
		if (_sshSession != null && _sshSession.isConnected()) {
			_sshSession.disconnect();
		}

	}
	
	/**
	 * 判断是否连接
	 * @return
	 */
	public boolean isConnected() {
		return (sftp != null) && sftp.isConnected() && !sftp.isClosed()
				&& (_sshSession != null) && _sshSession.isConnected();
	}
	
	
	
	/**
	 * 列出远程目录下的文件
	 * @param dir 远程目录名称
	 * @return
	 */
	private  Vector _list(String dir) {
		try {
			return sftp.ls(dir);
		} catch (Exception e) {
			return null;
		}
	}
	
	
	/**
	 * 列出当前文件目录下的文件
	 * @return
	 */
	private  Vector _list() {
		try {
			return sftp.ls(sftp.pwd());
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 列出指定目录下的文件
	 * @param dir
	 * @return
	 */
	public  List list(String dir) {
		try {
			Vector ls = _list(dir);
			return _buildFiles(ls);
		} catch (Exception e) {
			return null;
		}
	}
	/**
	 * 列出文件信息，文件列表list存放单个文件Map，包含文件名和文件是否目录
	 * @param ls 文件列表
	 * @return
	 * @throws Exception
	 */
	private  List _buildFiles(Vector ls) throws Exception {
		if (ls != null && ls.size() >= 0) {
			List list = new ArrayList();
			for (int i = 0; i < ls.size(); i++) {
				LsEntry f = (LsEntry) ls.get(i);
				String nm = f.getFilename();
				if (nm.equals(".") || nm.equals(".."))
					continue;
				SftpATTRS attr = f.getAttrs();
				Map m = new HashMap();
				if (attr.isDir()) {
					m.put("dir", new Boolean(true));
				} else {
					m.put("dir", new Boolean(false));
				}
				m.put("name", nm);
				list.add(m);
			}
			return list;
		}
		return null;
	}
	
	/**
	 * 列出文件列表
	 * @return
	 */
	public List list() {
		try {
			Vector ls = _list();
			return _buildFiles(ls);
		} catch (Exception e) {
			return null;
		}
	}
	
	/**
	 * 进入多级目录
	 * @param dirs  多级目录的路径
	 * @return
	 * @throws Exception
	 */
	public boolean cd(String dirs) throws Exception {
		try {
			String path = dirs;
			path = dealUnixPath(path);
			String pwd = sftp.pwd();//进入当前目录
			//当前目录则返回
			if (pwd.equals(path))
				return true;
			//从根目录进行查询
			sftp.cd(_root);
			//为根目录则返回
			if (_root.equals(dirs))
				return true;
			//一层一层进入目录
			String[] paths = path.split("/");
			for (int i = 0; i < paths.length; i++) {
				String dir = paths[i];
				if (isEmpty(dir))
					continue;
				sftp.cd(dir);
			}
			return true;
		} catch (Exception e) {
			throw e;
		}
	}
	
	/**
	 * 判断指定文件夹下是否存在指定的文件名
	 * @param remoteDir  查找远程根目录
	 * @param fileOrDir 文件或目录名
	 * @param sftp
	 * @return
	 * @throws Exception
	 */
	public  boolean isExist(String remoteDir, String fileOrDir) throws Exception {
		remoteDir = FilenameUtils.separatorsToUnix(remoteDir);
		boolean exist = false;
		try {
			Vector ls = _list(remoteDir);//列出文件
			//遍历查询文件是否存在
			if (ls != null && ls.size()>0) {
				for (int i = 0; i < ls.size(); i++) {
					LsEntry f = (LsEntry) ls.get(i);
					String nm = f.getFilename();
					if (nm.equals(fileOrDir)) {
						exist = true;
						break;
					}
				}
			}
			
		} catch (Exception e) {
			logger.error("判断文件是否存在异常"+e.getMessage(), e);
		}
		return exist;
	}
	//判断空
	public  boolean isEmpty(String s) {
		return s == null || "".equals(s.trim());
	}
	//上传文件
	public  boolean upload(File uploadFile,String fileName){
		boolean result = false;
		InputStream is = null;
		try {
			is = new FileInputStream(uploadFile);
			sftp.put(is, fileName);
			result = true;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		} finally {
			try {
				if (is != null) {
					is.close();
					is = null;
				}
			} catch (Exception e2) {
				logger.error(e2.getMessage(), e2);
			}
		}
		return result;
	}
	/**
	 * 下载文件
	 * @param dir 远程目录
	 * @param downloadFile 下载文件路径
	 * @param result
	 */
	public  boolean download(String dir, String remoteName,
			String local) {
		boolean result = false;
		FileOutputStream os  = null;
		try {
		if (!isEmpty(dir)) {
				sftp.cd(dir);
				os = new FileOutputStream(local);
				sftp.get(remoteName, os);
				result =  true;
				sftp.cd(_root);
		}
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e.getMessage());
		}finally{
			IOUtils.closeQuietly(os);
		}
		if (result){
			logger.info("下载文件成功:"+local);
		}
		return result;
	}
	/**
	 * 创建目录
	 * @param filepath 远程文件目录路径
	 * @return
	 * @throws Exception
	 */
	public  boolean mkDir(String remoteDir)
			throws Exception {
		boolean result = false;
		if (isEmpty(remoteDir)){
			logger.error("path can't be null!");
			return false;
		}
		try {
			remoteDir = FilenameUtils.separatorsToUnix(FilenameUtils.normalizeNoEndSeparator(remoteDir));
			if (remoteDir.startsWith("/")){
				//如果是绝对路径并在当前用户根目录下
				if (remoteDir.startsWith(_root)){
					remoteDir = remoteDir.substring(_root.length());
					sftp.cd(_root);
				}else{
					sftp.cd("/");//非当前用户目录下
				}
				//删除头部的/
				if (remoteDir.startsWith("/")){
					remoteDir = remoteDir.substring(1);
				}
			}else{
				sftp.cd(_root);//相对路径，进入用户根目录下
			}
			String[] paths = remoteDir.split("/");
			for (int i = 0; i < paths.length; i++) {
				String dirName = paths[i];
				String dirPath = FilenameUtils.separatorsToUnix(FilenameUtils.concat(sftp.pwd(), dirName));
				if (!remoteDirExists(dirPath)) {
					sftp.mkdir(dirName);
					sftp.cd(dirPath);
				}
			}
			result = true;
		} catch (Exception e) {
			logger.error(e.getMessage());
			result = false;
		}
		return result;
	}
	
	/**
	 * 重命名远程文件名
	 * @param remoteDir   远程目录
	 * @param oldFileName 原文件名
	 * @param newFileName 新文件名
	 * @param sftp  
	 * @return
	 * @throws Exception
	 */
	public boolean renameFileName(String remoteDir, String oldFileName,
			String newFileName) {
		boolean result = false;
		if (!remoteDir.startsWith("/")){
			remoteDir = FilenameUtils.concat(getUserRoot(), remoteDir);
		}
		try {
			// 如果文件已经存在，先删除文件
			if (this.isExist(remoteDir, newFileName)) {
				sftp.rm(FilenameUtils.separatorsToUnix(remoteDir+"/"+newFileName));
			}
			// 重命名文件
			String oldFilePath = FilenameUtils.separatorsToUnix(FilenameUtils.concat(remoteDir, oldFileName)); 
			String newFilepath = FilenameUtils.separatorsToUnix(FilenameUtils.concat(remoteDir, newFileName)); 
			logger.debug(String.format("重命名 [%s] 为 [%s]", oldFilePath,newFilepath));
			sftp.rename(oldFilePath,newFilepath);
			result = true;
		} catch (Exception e) {
			logger.error("重命名文件失败", e);
		}
		return result;
	}
	/**
	 * 删除文件
	 * @param deleteFile 删除的文件
	 * @return
	 * @throws Exception
	 */
	public  boolean rm(String deleteFile)
			throws Exception {
		try {
			Vector ls = _list();

			if (ls != null && ls.size() > 0) {
				for (int i = 0; i < ls.size(); i++) {
					LsEntry f = (LsEntry) ls.get(i);
					String nm = f.getFilename();
					if (!nm.equals(deleteFile)) {
						continue;
					}
					SftpATTRS attr = f.getAttrs();
					
					if (attr.isDir()) {
						if (rmdir(nm)) {
							sftp.rmdir(nm);
						}
					} else {
						sftp.rm(nm);
					}
				}
			}
			return true;
		} catch (Exception e) {
			throw e;
		}
	}
	/**
	 * 删除远程目录
	 * @param dir 远程目录
	 * @return
	 * @throws Exception
	 */
	private  boolean rmdir(String dir) throws Exception {
		try {
			//进入目录，删除下面所有文件
			sftp.cd(dir);
			//列出当前目录文件
			Vector ls = _list();
			//遍历，如果是目录，递归，如果是文件，删除
			if (ls != null && ls.size() > 0) {
				for (int i = 0; i < ls.size(); i++) {
					LsEntry f = (LsEntry) ls.get(i);
					String nm = f.getFilename();
					if (nm.equals(".") || nm.equals(".."))
						continue;
					SftpATTRS attr = f.getAttrs();
					//目录，继续递归
					if (attr.isDir()) {
						if (rmdir(nm)) {
							sftp.rmdir(nm);
						}
					} else {
						//删除文件
						sftp.rm(nm);
					}
				}
			}
			sftp.cd("..");
			return true;
		} catch (Exception e) {
			throw e;
		}
	}
	
	/**
	 * 查看远程文件是否存在
	 * @param remoteDir 远程目录
	 * @return
	 */
	private boolean remoteDirExists(String remoteDir) {
		try {
			sftp.cd(remoteDir);
			return true;
		} catch (SftpException e) {
			return false;
		}
	}
	@Override
	/**
	 * 查看远程文件是否存在
	 * @param remote 远程文件
	 * @return
	 */
	public boolean exists(String remote) {
		Vector v = null ;
		try {
			if (!remote.startsWith(_root)){
				remote = FilenameUtils.separatorsToUnix(FilenameUtils.concat(_root, remote));
			}
			v = sftp.ls(remote);
		} catch (SftpException e) {
			logger.error(e.getMessage()+" "+remote,e);
		}
		return v!=null&&v.size()==1;
	}
	/**
	 * 获取远程文件的上级目录
	 * @param remote 远程文件路径
	 * @return
	 */
	private String getRemoteParent(String remote){
		int index = remote.lastIndexOf("/");
		return remote.substring(0, index);
	}
	/**
	 * 获取远程文件名
	 * @param remote 远程文件路径
	 * @return
	 */
	public static String getRemoteName(String remote){
		int index = remote.lastIndexOf("/")+1;
		return remote.substring(index);
	}
	
	
	@Override
	public boolean download(String remote, String local) throws IOException {
		remote = FilenameUtils.normalize(remote);
		if (!remote.startsWith(_root)){
			remote = FilenameUtils.separatorsToUnix(FilenameUtils.concat(_root, remote));
		}
		logger.debug("remoteFile远程文件路径:"+remote);
		logger.debug("localFile本地文件路径:"+local);
		if (exists(remote)){
			return download(FilenameUtils.getFullPathNoEndSeparator(remote), FilenameUtils.getName(remote), local);
		}
		return false;
	}
	
	@Override
	/**
	 * 
	 */
	public boolean renameTo(String oldFile, String newName) {
		String dir = FilenameUtils.getFullPath(oldFile);
		String oldName = FilenameUtils.getName(oldFile);
		return renameFileName(dir, oldName, newName);
	}
	
	
	private String dealUnixPath(String path){
		String result = path;
		if (path==null){
			result=null;
		}
	   	if (!path.startsWith(_root)){
	  		result = FilenameUtils.separatorsToUnix(FilenameUtils.concat(_root, path));
	    }
		return result;
	}
	
	private String getUnixPath(String filepath){
		return FilenameUtils.separatorsToUnix(FilenameUtils.getFullPath(filepath));
	}
	
	public String getUserRoot(){
		return _root;
	}
}
